using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;

namespace Tomahawk.Runtime.Logic
{
    /// <summary>
    /// The World is collection of existing gameplay objects.
    /// It's implemented as a singleton so it's globally accesible across the entire application.
    /// It provides an interface to create, manage and destroy the gameplay objects
    /// </summary>
    public class XWorld : XObject
    {
        /// <summary>
        /// Private constructor (disallow creation of this type of objects)
        /// </summary>
        private XWorld()
        {
            this.worldObjects = new Dictionary<string, XObject>();
            this.worldObjectList = new List<XObject>();
            this.sceneObjectList = new List<XSceneComponent>();
            this.recentlyCreatedObjects = new List<XObject>();
            this.UpdateRequired = true;
        }

        #region "Singleton"

        private static XWorld instance;

        /// <summary>
        /// Return the unique instance allowed of and XWorld object
        /// </summary>
        public static XWorld Instance
        {
            get { return XWorld.instance; }
        }

        /// <summary>
        /// Static constructor: initializes the sigleton
        /// </summary>
        static XWorld()
        {
            //create the only instance
            instance = new XWorld();
            instance.Initialize();
        }

        #endregion

        #region "Object lists"

        /// <summary>
        /// List of created gameplay objects, xobjects will register themselves on this lists, 
        /// during their construction
        /// </summary>
        private List<XObject> recentlyCreatedObjects;

        /// <summary>
        /// Dictionary of the gameplay objects that exists at runtime
        /// </summary>
        private Dictionary<string, XObject> worldObjects;

        /// <summary>
        /// List of gameplay objects that exists at runtime
        /// </summary>
        private List<XObject> worldObjectList;

        /// <summary>
        /// List of scene objects
        /// </summary>
        private List<XSceneComponent> sceneObjectList;

        /// <summary>
        /// Get an object by its name
        /// </summary>
        public XObject GetObject(string name)
        {
            XObject retVal = null;
            if (!worldObjects.TryGetValue(name, out retVal))
            {
                //TODO: Logar error de objeto no encontrado!
            }
            return retVal;
        }

        /// <summary>
        /// Get all the objects of the desired type
        /// </summary>
        public List<T> GetOjects<T>() where T: XObject
        {
            List<T> objects = new List<T>();

            foreach (XObject obj in worldObjectList)
            {
                if (obj is T)
                {
                    objects.Add(obj as T);
                }
            }

            return objects;
        }

        internal void ResgisterLogicObjectCreation(XObject obj)
        {
            this.recentlyCreatedObjects.Add(obj);
        }

        #endregion

        #region "Object Instancing"

        /// <summary>
        /// Create a new object using an explicit name, but no template and no parent
        /// </summary>
        public XObject CreateObject(Type objectType, string name)
        {
            return CreateObject(true, objectType, null, name, "");
        }

        /// <summary>
        /// Create a new object using an explicit name, but no template and no parent
        /// </summary>
        public XObject CreateObject(bool autoInitialize, Type objectType, string name)
        {
            return CreateObject(autoInitialize, objectType, null, name, "");
        }

        /// <summary>
        /// Create a new object using an explicit name, but no template
        /// </summary>
        public XObject CreateObject(Type objectType, XObject parentObj, string name)
        {
            return CreateObject(true, objectType, parentObj, name, "");
        }

        /// <summary>
        /// Create a new object using an explicit name, but no template
        /// </summary>
        public XObject CreateObject(bool autoInitialize, Type objectType, XObject parentObj, string name)
        {
            return CreateObject(autoInitialize, objectType, parentObj, name, "");
        }

        /// <summary>
        /// Create a new object using no parent, no explicit name, and no template.
        /// </summary>
        public XObject CreateObject(Type objectType)
        {
            return CreateObject(true, objectType, null, "", "");
        }        
        
        /// <summary>
        /// Create a new object using no parent, no explicit name, and no template.
        /// </summary>
        public XObject CreateObject(bool autoInitialize, Type objectType)
        {
            return CreateObject(autoInitialize, objectType, null, "", "");
        }

        /// <summary>
        /// Create a new object using no explicit name, and no template.
        /// </summary>
        public XObject CreateObject(Type objectType, XObject parentObj)
        {
            return CreateObject(true, objectType, parentObj, "", "");
        }

        /// <summary>
        /// Create a new object using no explicit name, and no template.
        /// </summary>
        public XObject CreateObject(bool autoInitialize, Type objectType, XObject parentObj)
        {
            return CreateObject(autoInitialize, objectType, parentObj, "", "");
        }

        /// <summary>
        /// Create a new object using no parent, no explicit name, and no template.
        /// </summary>
        public XObject CreateObject(Type objectType, string name, string template)
        {
            return CreateObject(true, objectType, null, name, template);
        }

        /// <summary>
        /// Create a new object using no parent, no explicit name, and no template.
        /// </summary>
        public XObject CreateObject(bool autoInitialize, Type objectType, string name, string template)
        {
            return CreateObject(autoInitialize, objectType, null, name, template);
        }

        /// <summary>
        /// Create a new object using parent, name, and template.
        /// </summary>
        public XObject CreateObject(Type objectType, XObject parentObj, string name, string template)
        {
            return CreateObject(true, objectType, parentObj, name, template);
        }

        /// <summary>
        /// Instantiate an object of the specified type, using the name and the template specified.
        /// If no template is specified, or the template doesn't exist, the object will be intializated 
        /// with it's own default values for it's properties. If a template is found, the properties 
        /// will be loaded with the values from the template
        /// </summary>
        public XObject CreateObject(bool autoInitialize, Type objectType, XObject parentObj, string name, string template)
        {
            XObject newXObj = null;

            //we can only instantiate types derived from XObject
            if (!typeof(XObject).IsAssignableFrom(objectType))
                throw new Exception("XWorld exception: Cannot create objects of type: " + objectType.Name + " because if doesn't derive from XObject");

            if (template == "")
            {
                //instantiate
                newXObj = objectType.Assembly.CreateInstance(objectType.FullName) as XObject;
                //Sets the autoInit flag
                newXObj.AutoInit = autoInitialize;
            }
            else
            {
                try
                {
                    //Instancing using XMLSerialize
                    FileStream fs = new FileStream(template, FileMode.Open, FileAccess.Read);
                    XmlSerializer x = new XmlSerializer(objectType);
                    newXObj = (XObject)x.Deserialize(fs);
                    //Sets the autoInit flag
                    newXObj.AutoInit = autoInitialize;
                }
                catch (Exception ex)
                {
                    //TODO: Handling this exception propperly!!
                    throw ex;
                }

            }

            //add to the array
            registerObject(parentObj, name, newXObj);

            //initialize all recently created objects
            initializeRecentlyCreatedObjects();

            //return an object that it's ready-for-action
            return newXObj;
        }

        /// <summary>
        /// Registers the logic object in the world
        /// </summary>
        internal void registerObject(XObject parentObj, string name, XObject newXObj)
        {
            //generate the name if not specified
            if (name == "") name = generateObjectName(newXObj.GetType());

            //set the object initial properties
            newXObj.SetParent(parentObj);
            newXObj.SetName(name);

            //register the object in the array
            this.worldObjects.Add(name, newXObj);
            this.worldObjectList.Add(newXObj);

            //if scene, add to scene list
            XSceneComponent sceneCmp = newXObj as XSceneComponent;
            if (sceneCmp != null)
            {
                this.sceneObjectList.Add(sceneCmp);
            }

            //initialize the object
            if (newXObj.AutoInit)
                newXObj.Initialize();
        }

        /// <summary>
        /// Counter used to automatically name the objects
        /// </summary>
        private static int objectIndex = 0;

        /// <summary>
        /// Generates a named for no explicitly named object based on the type name and a numeric index.
        /// Makes sure the name it's not already used by any other gameplay object
        /// </summary>
        private string generateObjectName(Type objType)
        {
            string candidateName = "";
            XObject existingObj = null;
            bool validName = false;
            while (!validName)
            {
                candidateName = string.Format("{0}_{1}", objType.Name, objectIndex++);
                validName = !worldObjects.TryGetValue(candidateName, out existingObj);
            }
            return candidateName;
        }

        /// <summary>
        /// Initialize all pendant objects
        /// </summary>
        private void initializeRecentlyCreatedObjects()
        {
            //make a list of the objects that are not already initialized
            List<XObject> objectsToInitialize = new List<XObject>();
            foreach (XObject obj in this.recentlyCreatedObjects)
            {
                if ((!obj.Initialized) && (obj.AutoInit))
                    objectsToInitialize.Add(obj);
            }

            //clear the recentlyCreatedObject list
            this.recentlyCreatedObjects.Clear();

            //register & initialize the pendant objects
            foreach (XObject obj in objectsToInitialize)
            {
                this.registerObject(this, "", obj);
            }

        }

        #endregion

        #region "Object Destruction"

        /// <summary>
        /// Defered destruction of gameplay objects. 
        /// This will recursivelly mark all objects that depend on the specified one as pendant for destruction.
        /// </summary>
        internal void DeferedDestroyObject(XObject objDestroy)
        {
            //destroy children recursivelly
            foreach (XObject obj in worldObjects.Values)
            {
                if (obj.Parent == objDestroy)
                    DeferedDestroyObject(obj);
            }

            //mark the object itself as destroyed
            objDestroy.MarkForDestruction();
        }

        /// <summary>
        /// Iterate thourgh all the existing objects and detroy thoose which are marked for destruction
        /// </summary>
        private void destroyMarkedObjects()
        {
            //Remove from the array all objects that are marked for destruction
            for (int i = worldObjectList.Count - 1; i >= 0; i--)
            {
                XObject obj = worldObjectList[i];
                if (obj.MarkedForDestruction)
                {
                    obj.OnBeforeDestroy();

                    //remove from both lists
                    this.worldObjects.Remove(obj.Name);
                    this.worldObjectList.Remove(obj);

                    obj.OnAfterDestroy();
                }
            }
        }

        #endregion

        #region "Gameplay World Update"

        /// <summary>
        /// Update of the gameplay world logic. 
        /// It will update all of the existing game objects
        /// </summary>
        protected override void OnUpdate(float elapsedTime)
        {
            base.OnUpdate(elapsedTime);

            //Remove from the array all objects that are marked for destruction
            destroyMarkedObjects();

            //Update all objects in the array,
            //not using foreach, to allow creation of new logic objects within the update method.
            for (int i = 0; i < this.worldObjectList.Count; i++)
            {
                try
                {
                    //update only ready objects
                    XObject updateObj = this.worldObjectList[i];
                    if (updateObj != this)
                    {
                        if (updateObj.IsReady) updateObj.Update(elapsedTime);
                    }
                }
                catch (Debug.Exceptions.XEngineException ex)
                { 
                    //if during the update phase an exception is raised
                    //continue with the game execution, except if it's Critical
                    if (ex.Severity == Tomahawk.Runtime.Debug.Exceptions.ExceptionSeverity.Critical)
                        throw ex;
                }
            }
        }

        #endregion

        #region "Render Events"

        /// <summary>
        /// Tick the render event in the renderable objects
        /// </summary>
        internal void PreRender()
        {
            foreach (XSceneComponent cmp in this.sceneObjectList)
            {
                cmp.PreRender();
            }
        }

        #endregion

    }

}
